package com.ceair.config;

import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.AssociationDirection;
import org.flowable.bpmn.model.GraphicInfo;
import org.flowable.image.impl.DefaultProcessDiagramCanvas;
import org.flowable.image.util.ReflectUtil;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;

/**
 * @author wangbaohai
 * @ClassName ProcessDiagramConfig
 * @description: 流程图配置类
 * @date 2025年05月05日
 * @version: 1.0.0
 */
@Slf4j
public class CustomProcessDiagramCanvas extends DefaultProcessDiagramCanvas {

    // 定义走过流程连线颜色为绿色
    protected static Color HIGHLIGHT_SequenceFlow_COLOR = Color.GREEN;

    // 设置未走过流程的连接线颜色
    protected static Color CONNECTION_COLOR = Color.BLACK;

    // 设置flows连接线字体颜色red
    protected static Color LABEL_COLOR = new Color(0, 0, 0);

    // 高亮显示task框颜色
    protected static Color HIGHLIGHT_COLOR = Color.GREEN;
    protected static Color HIGHLIGHT_COLOR1 = Color.RED;

    // 设置任务框颜色
    protected static Color EVENT_COLOR = new Color(255, 255, 255);

    /**
     * 构造函数用于初始化自定义流程图画布
     *
     * @param width              画布的宽度
     * @param height             画布的高度
     * @param minX               画布的最小X坐标
     * @param minY               画布的最小Y坐标
     * @param imageType          图像类型
     * @param activityFontName   活动字体名称
     * @param labelFontName      标签字体名称
     * @param annotationFontName 注释字体名称
     * @param customClassLoader  自定义类加载器
     */
    public CustomProcessDiagramCanvas(int width, int height, int minX, int minY, String imageType,
                                      String activityFontName, String labelFontName, String annotationFontName,
                                      ClassLoader customClassLoader) {
        // 调用父类构造函数进行初始化
        super(width, height, minX, minY, imageType, activityFontName, labelFontName, annotationFontName,
                customClassLoader);
        // 初始化画布
        this.initialize(imageType);
    }

    /**
     * 初始化流程图画布并配置图形绘制环境。
     * <p>
     * 该方法负责：
     * - 根据图像类型创建 BufferedImage 实例
     * - 设置画布背景、字体样式、抗锯齿等绘图参数
     * - 加载所有流程图所需的图标资源（如任务、事件、连接线箭头等）
     *
     * @param imageType 图像类型，用于判断是否启用透明通道（"png" 启用）
     */
    @Override
    public void initialize(String imageType) {
        // 初始化画布：根据图像类型选择是否启用透明通道
        int bufferedImageType = "png".equalsIgnoreCase(imageType)
                ? BufferedImage.TYPE_INT_ARGB   // 含透明通道（PNG）
                : BufferedImage.TYPE_INT_RGB;   // 不含透明通道（非PNG）

        // 创建指定大小的图像缓冲区作为绘图画布
        this.processDiagram = new BufferedImage(this.canvasWidth, this.canvasHeight, bufferedImageType);

        // 获取 Graphics2D 对象用于后续绘图操作
        this.g = this.processDiagram.createGraphics();

        // 非PNG格式下手动设置背景为透明并清空内容
        if (!"png".equalsIgnoreCase(imageType)) {
            // 设置背景颜色为透明（白色+0透明度）
            this.g.setBackground(new Color(255, 255, 255, 0));
            // 清除当前画布区域，确保背景干净
            this.g.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
        }

        // 启用抗锯齿渲染，提高图形绘制质量
        this.g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        // 设置默认绘图颜色为黑色
        this.g.setPaint(Color.BLACK);

        // 创建主活动字体（加粗 14 号字体）
        Font font = new Font(this.activityFontName, Font.BOLD, 14);
        this.g.setFont(font);             // 应用字体到画布
        this.fontMetrics = this.g.getFontMetrics();  // 获取字体度量信息用于文本布局

        // 设置标签文字字体（加粗 15 号）
        LABEL_FONT = new Font(this.labelFontName, Font.BOLD, 15);

        // 设置注释文字字体（普通 11 号）
        ANNOTATION_FONT = new Font(this.annotationFontName, Font.PLAIN, 11);

        // 加载流程图所需的所有图标资源
        try {
            // 用户任务图标
            USERTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/userTask.png",
                    this.customClassLoader));

            // 脚本任务图标
            SCRIPTTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/scriptTask.png",
                    this.customClassLoader));

            // 服务任务图标
            SERVICETASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/serviceTask.png",
                    this.customClassLoader));

            // 接收任务图标
            RECEIVETASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/receiveTask.png",
                    this.customClassLoader));

            // 发送任务图标
            SENDTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/sendTask.png",
                    this.customClassLoader));

            // 案例任务图标
            CASETASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/caseTask.png",
                    this.customClassLoader));

            // 手动任务图标
            MANUALTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/manualTask.png",
                    this.customClassLoader));

            // 业务规则任务图标
            BUSINESS_RULE_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/businessRuleTask.png"
                    , this.customClassLoader));

            // Shell任务图标
            SHELL_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/shellTask.png",
                    this.customClassLoader));

            // 决策任务图标
            DMN_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/dmnTask.png",
                    this.customClassLoader));

            // Camel任务图标
            CAMEL_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/camelTask.png",
                    this.customClassLoader));

            // HTTP任务图标
            HTTP_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/httpTask.png",
                    this.customClassLoader));

            // 定时器图标
            TIMER_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/timer.png", this.customClassLoader));

            // 补偿抛出图标
            COMPENSATE_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/compensate-throw.png",
                    this.customClassLoader));

            // 补偿捕获图标
            COMPENSATE_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/compensate.png",
                    this.customClassLoader));

            // 条件捕获图标
            CONDITIONAL_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/conditional.png",
                    this.customClassLoader));

            // 错误抛出图标
            ERROR_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/error-throw.png",
                    this.customClassLoader));

            // 错误捕获图标
            ERROR_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/error.png",
                    this.customClassLoader));

            // 升级抛出图标
            ESCALATION_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/escalation-throw.png",
                    this.customClassLoader));

            // 升级捕获图标
            ESCALATION_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/escalation.png",
                    this.customClassLoader));

            // 消息抛出图标
            MESSAGE_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/message-throw.png",
                    this.customClassLoader));

            // 消息捕获图标
            MESSAGE_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/message.png",
                    this.customClassLoader));

            // 信号抛出图标
            SIGNAL_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/signal-throw.png",
                    this.customClassLoader));

            // 信号捕获图标
            SIGNAL_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/signal.png",
                    this.customClassLoader));

        } catch (IOException e) {
            // 日志记录加载失败原因
            log.warn("Could not load image for process diagram creation: {}", e.getMessage());
        }
    }

    /**
     * 重写绘制连接线的方法，支持多种类型连线样式、高亮显示及方向箭头。
     *
     * @param xPoints              X轴坐标点数组
     * @param yPoints              Y轴坐标点数组
     * @param conditional          是否为条件流向
     * @param isDefault            是否为默认流向
     * @param connectionType       连接线类型（如 "association"）
     * @param associationDirection 关联方向（ONE, BOTH）
     * @param highLighted          是否高亮显示
     * @param scaleFactor          缩放比例因子，用于图像缩放处理
     */
    @Override
    public void drawConnection(int[] xPoints, int[] yPoints, boolean conditional, boolean isDefault,
                               String connectionType, AssociationDirection associationDirection, boolean highLighted,
                               double scaleFactor) {
        // 保存原始画笔和线条样式，便于后续恢复
        Paint originalPaint = g.getPaint();
        Stroke originalStroke = g.getStroke();

        // 设置默认连接线颜色
        g.setPaint(CONNECTION_COLOR);

        // 根据连接线类型设置不同的线条样式
        if ("association".equals(connectionType)) {
            // 如果是关联线，使用虚线样式
            g.setStroke(ASSOCIATION_STROKE);
        } else if (highLighted) {
            // 如果是高亮状态，使用绿色粗线样式
            g.setPaint(HIGHLIGHT_SequenceFlow_COLOR);
            g.setStroke(HIGHLIGHT_FLOW_STROKE);
        }

        // 绘制主连接线段：从起点到终点依次绘制折线
        for (int i = 1; i < xPoints.length; i++) {
            drawLine(xPoints[i - 1], yPoints[i - 1], xPoints[i], yPoints[i]);
        }

        // 如果是默认流向，绘制默认箭头指示器
        if (isDefault) {
            Line2D.Double line = createLine(xPoints[0], yPoints[0], xPoints[1], yPoints[1]);
            drawDefaultSequenceFlowIndicator(line, scaleFactor);
        }

        // 如果是条件流向，绘制条件箭头指示器
        if (conditional) {
            Line2D.Double line = createLine(xPoints[0], yPoints[0], xPoints[1], yPoints[1]);
            drawConditionalSequenceFlowIndicator(line, scaleFactor);
        }

        // 如果是单向或双向关联，绘制箭头头部
        if (associationDirection == AssociationDirection.ONE || associationDirection == AssociationDirection.BOTH) {
            Line2D.Double line = createLine(
                    xPoints[xPoints.length - 2],
                    yPoints[xPoints.length - 2],
                    xPoints[xPoints.length - 1],
                    yPoints[xPoints.length - 1]
            );
            drawArrowHead(line, scaleFactor);
        }

        // 如果是双向关联，再反向绘制一个箭头
        if (associationDirection == AssociationDirection.BOTH) {
            Line2D.Double line = createLine(xPoints[1], yPoints[1], xPoints[0], yPoints[0]);
            drawArrowHead(line, scaleFactor);
        }

        // 恢复画笔与线条样式至初始状态
        g.setPaint(originalPaint);
        g.setStroke(originalStroke);
    }

    /**
     * 绘制流程图中的文本标签。
     * <p>
     * 该方法负责：
     * - 根据给定文本内容和图形信息绘制多行文本
     * - 支持居中对齐与左对齐两种方式
     * - 使用当前画笔设置的字体和颜色进行绘制
     * - 自动换行处理，最大宽度为 wrapWidth（100像素）
     *
     * @param text        要绘制的文本内容
     * @param graphicInfo 图形位置信息对象
     * @param centered    是否居中显示文本
     */
    @Override
    public void drawLabel(String text, GraphicInfo graphicInfo, boolean centered) {
        // 行间距系数，默认为1倍
        float interline = 1.0f;

        try {
            // 只有非空字符串才进行绘制
            if (text != null && !text.isEmpty()) {
                // 保存当前画笔和字体状态，便于绘制结束后恢复
                Paint originalPaint = g.getPaint();
                Font originalFont = g.getFont();

                // 设置标签专用颜色和字体
                g.setPaint(LABEL_COLOR);
                g.setFont(LABEL_FONT);

                int wrapWidth = 100; // 每行最大宽度，超过则换行
                int textY = (int) graphicInfo.getY(); // 初始 Y 坐标

                // 创建带样式属性的字符串，用于支持复杂排版
                AttributedString as = new AttributedString(text);
                as.addAttribute(TextAttribute.FOREGROUND, g.getPaint()); // 文本颜色
                as.addAttribute(TextAttribute.FONT, g.getFont());       // 字体样式

                // 获取字符迭代器和字体渲染上下文
                AttributedCharacterIterator aci = as.getIterator();
                FontRenderContext frc = new FontRenderContext(null, true, false);

                // 使用 LineBreakMeasurer 实现自动换行功能
                LineBreakMeasurer lbm = new LineBreakMeasurer(aci, frc);

                // 循环绘制每一行文本
                while (lbm.getPosition() < text.length()) {
                    TextLayout tl = lbm.nextLayout(wrapWidth); // 获取一行布局
                    textY += (int) tl.getAscent(); // 移动到文字顶部基准线

                    Rectangle2D bb = tl.getBounds(); // 获取当前行边界框
                    double tX = graphicInfo.getX(); // 起始 X 坐标

                    // 如果需要居中显示，则调整 X 坐标使其水平居中
                    if (centered) {
                        tX += (int) (graphicInfo.getWidth() / 2 - bb.getWidth() / 2);
                    }

                    // 在指定坐标上绘制当前行文本
                    tl.draw(g, (float) tX, textY);

                    // 更新 Y 坐标：移动下一行的基线位置
                    textY += (int) (tl.getDescent() + tl.getLeading() + (interline - 1.0f) * tl.getAscent());
                }

                // 恢复原始字体和画笔颜色
                g.setFont(originalFont);
                g.setPaint(originalPaint);
            }
        } catch (Exception e) {
            // 出现任何异常时记录日志并继续执行
            log.warn("绘制标签失败，文本内容: [{}], 异常原因: {}", text, e.getMessage());
        }
    }

    /**
     * 绘制高亮显示的任务矩形框边框。
     * <p>
     * 该方法用于绘制一个带有圆角的矩形边框，表示任务已被高亮选中。
     * - 使用预定义的高亮颜色和粗线样式进行绘制
     * - 在绘制前后会保存并恢复画布状态（颜色和笔触）
     *
     * @param x      矩形左上角 X 坐标
     * @param y      矩形左上角 Y 坐标
     * @param width  矩形宽度
     * @param height 矩形高度
     */
    @Override
    public void drawHighLight(int x, int y, int width, int height) {
        try {
            // 保存当前画笔颜色和线条样式，以便绘制完成后恢复
            Paint originalPaint = g.getPaint();
            Stroke originalStroke = g.getStroke();

            // 设置高亮颜色和粗线样式
            g.setPaint(HIGHLIGHT_COLOR);
            g.setStroke(THICK_TASK_BORDER_STROKE);

            // 创建一个带圆角的矩形区域（圆角大小为 20x20）
            RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, 20, 20);

            // 在画布上绘制矩形边框
            g.draw(rect);

            // 恢复原始画笔颜色和线条样式，保证不影响后续绘制
            g.setPaint(originalPaint);
            g.setStroke(originalStroke);
        } catch (Exception e) {
            // 出现异常时记录警告信息，避免流程图生成中断
            log.warn("绘制高亮框失败，位置: ({}, {}), 尺寸: {}x{}, 异常原因: {}", x, y, width, height, e.getMessage());
        }
    }

    /**
     * 绘制任务框图形。
     * <p>
     * 该方法用于绘制流程图中的任务节点矩形框，并支持：
     * - 圆角矩形填充与边框绘制
     * - 边框粗细控制（thickBorder）
     * - 文本居中显示（仅当 scaleFactor == 1.0 时生效）
     *
     * @param name        要绘制的任务名称文本
     * @param graphicInfo 图形位置信息对象（包含坐标和尺寸）
     * @param thickBorder 是否使用粗边框样式
     * @param scaleFactor 缩放比例因子，影响绘制大小
     */
    @Override
    protected void drawTask(String name, GraphicInfo graphicInfo, boolean thickBorder, double scaleFactor) {
        try {
            // 保存当前画笔颜色，便于后续恢复原始状态
            Paint originalPaint = g.getPaint();

            // 提取图形绘制区域的位置和尺寸信息
            int x = (int) graphicInfo.getX();
            int y = (int) graphicInfo.getY();
            int width = (int) graphicInfo.getWidth();
            int height = (int) graphicInfo.getHeight();

            // 设置任务框填充颜色（统一为 TASK_BOX_COLOR）
            g.setPaint(TASK_BOX_COLOR);

            // 根据是否为粗边框决定圆角大小
            int arcR = thickBorder ? 3 : 6;

            // 创建带圆角的矩形区域（用于任务框形状）
            RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, arcR, arcR);

            // 填充任务框背景色
            g.fill(rect);

            // 设置任务框边框颜色
            g.setPaint(TASK_BORDER_COLOR);

            // 如果是粗边框，则临时更换画笔样式进行绘制
            if (thickBorder) {
                Stroke originalStroke = g.getStroke();              // 保存原线条样式
                g.setStroke(THICK_TASK_BORDER_STROKE);              // 使用粗线样式
                g.draw(rect);                                     // 绘制边框
                g.setStroke(originalStroke);                        // 恢复原线条样式
            } else {
                // 否则直接使用默认线条样式绘制边框
                g.draw(rect);
            }

            // 恢复原始画笔颜色，确保不影响其他图形绘制
            g.setPaint(originalPaint);

            // 只有在非缩放状态下且存在文本内容时才绘制文字
            if (scaleFactor == 1.0 && name != null && !name.isEmpty()) {
                // 计算文本绘制区域的宽度和高度
                int boxWidth = width - (2 * TEXT_PADDING);
                int boxHeight = height - 16 - ICON_PADDING - ICON_PADDING - MARKER_WIDTH - 2 - 2;

                // 计算文本水平居中 X 坐标
                int boxX = x + width / 2 - boxWidth / 2;

                // 计算文本垂直居中 Y 坐标（考虑图标和标记的偏移量）
                int boxY = y + height / 2 - boxHeight / 2 + ICON_PADDING + ICON_PADDING - 2 - 2;

                // 调用工具方法绘制多行居中文本
                drawMultilineCentredText(name, boxX, boxY, boxWidth, boxHeight);
            }
        } catch (Exception e) {
            // 出现异常时记录警告日志，避免中断流程图生成
            log.warn("绘制任务框失败，任务名: [{}], 异常原因: {}", name, e.getMessage());
        }
    }

    /**
     * 绘制流程图中的开始事件图形（圆形）。
     * <p>
     * 该方法负责：
     * - 使用预定义颜色绘制一个填充圆表示开始事件
     * - 若存在图标图像，则将其居中绘制于圆内
     * - 支持根据缩放因子调整图像大小
     *
     * @param graphicInfo 图形位置和尺寸信息
     * @param image       要绘制的图标图像（可为 null）
     * @param scaleFactor 缩放比例因子，用于图像缩放计算
     */
    @Override
    public void drawStartEvent(GraphicInfo graphicInfo, BufferedImage image, double scaleFactor) {
        try {
            // 保存当前画笔颜色，便于后续恢复
            Paint originalPaint = g.getPaint();

            // 设置填充颜色并创建椭圆（圆形）区域
            g.setPaint(EVENT_COLOR);
            Ellipse2D circle = new Ellipse2D.Double(
                    graphicInfo.getX(),
                    graphicInfo.getY(),
                    graphicInfo.getWidth(),
                    graphicInfo.getHeight()
            );

            // 填充圆形背景
            g.fill(circle);

            // 设置边框颜色并绘制圆形轮廓
            g.setPaint(EVENT_BORDER_COLOR);
            g.draw(circle);

            // 恢复原始画笔颜色
            g.setPaint(originalPaint);

            // 如果提供了图标图像，则进行绘制
            if (image != null) {
                // 计算图像绘制起始点 X 坐标，使其水平居中
                int imageX = (int) Math.round(
                        graphicInfo.getX() + (graphicInfo.getWidth() / 2) -
                                (image.getWidth() / (2 * scaleFactor))
                );

                // 计算图像绘制起始点 Y 坐标，使其垂直居中
                int imageY = (int) Math.round(
                        graphicInfo.getY() + (graphicInfo.getHeight() / 2) -
                                (image.getHeight() / (2 * scaleFactor))
                );

                // 绘制缩放后的图像
                g.drawImage(
                        image,
                        imageX,
                        imageY,
                        (int) (image.getWidth() / scaleFactor),
                        (int) (image.getHeight() / scaleFactor),
                        null
                );
            }
        } catch (Exception e) {
            // 异常时记录警告日志，避免中断流程图生成
            log.warn("绘制开始事件失败，位置: ({}, {}), 异常原因: {}",
                    graphicInfo.getX(), graphicInfo.getY(), e.getMessage());
        }
    }

    /**
     * 绘制流程图中的结束事件图形（空心圆）。
     * <p>
     * 该方法负责：
     * - 使用预定义颜色绘制一个填充圆形表示结束事件
     * - 根据缩放因子设置不同的边框粗细
     * - 在绘制前后保存并恢复画布状态（颜色和线条样式）
     *
     * @param graphicInfo 图形位置和尺寸信息
     * @param scaleFactor 缩放比例因子，影响图像绘制大小
     */
    @Override
    public void drawNoneEndEvent(GraphicInfo graphicInfo, double scaleFactor) {
        try {
            // 保存当前画笔颜色和线条样式，便于后续恢复
            Paint originalPaint = g.getPaint();
            Stroke originalStroke = g.getStroke();

            // 设置圆形填充颜色
            g.setPaint(EVENT_COLOR);

            // 创建椭圆（圆形）区域
            Ellipse2D circle = new Ellipse2D.Double(
                    graphicInfo.getX(),
                    graphicInfo.getY(),
                    graphicInfo.getWidth(),
                    graphicInfo.getHeight()
            );

            // 填充圆形背景
            g.fill(circle);

            // 设置边框颜色
            g.setPaint(EVENT_BORDER_COLOR);

            // 根据缩放比例设置不同粗细的边框
            if (scaleFactor == 1.0) {
                // 正常比例下使用预定义粗线样式
                g.setStroke(END_EVENT_STROKE);
            } else {
                // 缩放状态下使用固定宽度的粗线样式（2像素）
                g.setStroke(new BasicStroke(2.0f));
            }

            // 绘制圆形边框
            g.draw(circle);

            // 恢复原始画笔颜色和线条样式
            g.setStroke(originalStroke);
            g.setPaint(originalPaint);
        } catch (Exception e) {
            // 异常时记录警告日志，避免中断流程图生成
            log.warn("绘制结束事件失败，位置: ({}, {}), 异常原因: {}",
                    graphicInfo.getX(), graphicInfo.getY(), e.getMessage());
        }
    }

    /**
     * 绘制当前任务位置的高亮矩形框。
     * <p>
     * 该方法用于绘制一个带有圆角的矩形边框，表示当前任务正在执行或被选中。
     * - 使用预定义的高亮颜色（红色）和粗线样式进行绘制
     * - 在绘制前后会保存并恢复画布状态（颜色和笔触），避免影响其他图形
     *
     * @param x      矩形左上角 X 坐标
     * @param y      矩形左上角 Y 坐标
     * @param width  矩形宽度
     * @param height 矩形高度
     */
    public void drawHighLightNow(int x, int y, int width, int height) {
        try {
            // 保存当前画笔颜色和线条样式，便于绘制完成后恢复
            Paint originalPaint = g.getPaint();
            Stroke originalStroke = g.getStroke();

            // 设置高亮颜色为红色（HIGHLIGHT_COLOR1）
            g.setPaint(HIGHLIGHT_COLOR1);

            // 设置粗线样式用于高亮边框
            g.setStroke(THICK_TASK_BORDER_STROKE);

            // 创建一个带圆角的矩形区域（固定圆角大小为 20x20）
            RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, 20, 20);

            // 绘制矩形边框
            g.draw(rect);

            // 恢复原始画笔颜色和线条样式，保证不影响后续绘图
            g.setPaint(originalPaint);
            g.setStroke(originalStroke);
        } catch (Exception e) {
            // 异常时记录警告日志，避免中断流程图生成
            log.warn("绘制当前任务高亮框失败，位置: ({}, {}), 尺寸: {}x{}, 异常原因: {}",
                    x, y, width, height, e.getMessage());
        }
    }

    /**
     * 绘制流程图中的结束任务高亮矩形框。
     * <p>
     * 该方法用于绘制一个带有圆角的矩形边框，表示流程结束节点。
     * - 使用预定义的高亮颜色（绿色）和粗线样式进行绘制
     * - 在绘制前后保存并恢复画布状态（颜色和线条样式），避免影响其他图形
     *
     * @param x      矩形左上角 X 坐标
     * @param y      矩形左上角 Y 坐标
     * @param width  矩形宽度
     * @param height 矩形高度
     */
    public void drawHighLightEnd(int x, int y, int width, int height) {
        try {
            // 保存当前画笔颜色和线条样式，便于后续恢复
            Paint originalPaint = g.getPaint();
            Stroke originalStroke = g.getStroke();

            // 设置高亮颜色为绿色（HIGHLIGHT_COLOR）
            g.setPaint(HIGHLIGHT_COLOR);

            // 设置粗线样式用于高亮边框
            g.setStroke(THICK_TASK_BORDER_STROKE);

            // 创建带圆角的矩形区域（固定圆角大小为 20x20）
            RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, 20, 20);

            // 在画布上绘制矩形边框
            g.draw(rect);

            // 恢复原始画笔颜色和线条样式，保证不影响后续绘图操作
            g.setPaint(originalPaint);
            g.setStroke(originalStroke);
        } catch (Exception e) {
            // 出现异常时记录警告日志，避免中断流程图生成
            log.warn("绘制结束节点高亮框失败，位置: ({}, {}), 尺寸: {}x{}, 异常原因: {}",
                    x, y, width, height, e.getMessage());
        }
    }

    /**
     * 快捷绘制一条直线段。
     *
     * @param x1 起始X坐标
     * @param y1 起始Y坐标
     * @param x2 结束X坐标
     * @param y2 结束Y坐标
     */
    private void drawLine(int x1, int y1, int x2, int y2) {
        g.draw(new Line2D.Double(x1, y1, x2, y2));
    }

    /**
     * 构造一个新的 Line2D.Double 对象。
     *
     * @param x1 起始X坐标
     * @param y1 起始Y坐标
     * @param x2 结束X坐标
     * @param y2 结束Y坐标
     * @return 构造完成的 Line2D.Double 线段对象
     */
    private Line2D.Double createLine(int x1, int y1, int x2, int y2) {
        return new Line2D.Double(x1, y1, x2, y2);
    }

}
